# apps/tags/tests.py
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status
from django.contrib.auth.models import User
from .models import Tag
from ..users.models import QBUser


class TagAPITest(APITestCase):
    """
    标签 API 测试类
    覆盖 TagViewSet 的所有功能：CRUD、过滤、搜索、排序、自定义动作
    """

    def setUp(self):
        # 创建测试用户并登录
        self.QBuser = QBUser.objects.create_user(username='testuser', password='testpass')
        self.client.force_authenticate(user=self.QBuser)

        # 创建测试标签数据
        self.tag1 = Tag.objects.create(
            name="函数",
            category="知识点",
            description="数学中的函数概念",
            used_count=5
        )
        self.tag2 = Tag.objects.create(
            name="方程",
            category="知识点",
            description="代数方程",
            used_count=3
        )
        self.tag3 = Tag.objects.create(
            name="光合作用",
            category="知识点",
            description="植物的光合作用过程",
            used_count=8
        )
        self.tag4 = Tag.objects.create(
            name="诗词鉴赏",
            category="题型",
            description="古诗词赏析方法",
            used_count=0
        )

        # URL 别名
        self.list_url = reverse('tag-list')  # /tags/
        self.detail_url = lambda pk: reverse('tag-detail', kwargs={'pk': pk})
        self.categories_url = reverse('tag-categories')  # /tags/categories/
        self.by_category_url = lambda cat: reverse('tag-by-category', kwargs={'category': cat})
        self.increment_url = lambda pk: reverse('tag-increment', kwargs={'pk': pk})
        print(
            self.list_url,
            self.detail_url(self.tag1.id),
            self.categories_url,
            self.by_category_url('知识点'),
            self.increment_url(self.tag1.id)
        )

    def test_create_tag(self):
        """测试创建标签"""
        data = {
            "name": "牛顿定律",
            "category": "知识点",
            "description": "经典力学三大定律"
        }
        response = self.client.post(self.list_url, data, format='json')
        print("响应数据:", response.data)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['name'], data['name'])
        self.assertEqual(response.data['category'], data['category'])
        self.assertEqual(response.data['used_count'], 0)
        self.assertIn('created_at', response.data)
        # 确保只读字段未被修改
        self.assertNotIn('used_count', data)  # 创建时不传 used_count
        tag = Tag.objects.get(id=response.data['id'])
        self.assertEqual(tag.used_count, 0)

    def test_create_tag_duplicate_name_category(self):
        """测试创建重复的 name+category 组合，应失败"""
        data = {
            "name": "函数",
            "category": "知识点"
        }
        response = self.client.post(self.list_url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        print("响应数据:", response.data)
        self.assertIn('name', response.data)
        self.assertIn('该标签名称与分类的组合已存在。', str(response.data['name']))

    def test_list_tags(self):
        """测试获取标签列表（使用 TagListSerializer）"""
        response = self.client.get(self.list_url)
        print("响应数据:", response.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 4)
        first_tag = response.data['results'][0]
        expected_fields = {'id', 'name', 'category', 'used_count','created_at','updated_at'}
        self.assertEqual(set(first_tag.keys()), expected_fields)
        self.assertNotIn('description', first_tag)

    def test_retrieve_tag(self):
        """测试获取单个标签详情（使用 TagDetailSerializer）"""
        response = self.client.get(self.detail_url(self.tag1.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        print("响应数据:", response.data)
        self.assertEqual(response.data['name'], self.tag1.name)
        self.assertEqual(response.data['category'], self.tag1.category)
        self.assertEqual(response.data['description'], self.tag1.description)
        self.assertEqual(response.data['used_count'], self.tag1.used_count)
        self.assertIn('created_at', response.data)
        self.assertIn('updated_at', response.data)

    def test_update_tag(self):
        """测试更新标签"""
        data = {
            "name": "高级函数",
            "category": "知识点",
            "description": "包括极限、导数等"
        }
        response = self.client.put(self.detail_url(self.tag1.id), data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.tag1.refresh_from_db()
        self.assertEqual(self.tag1.name, "高级函数")
        self.assertEqual(self.tag1.category, "知识点")  # category 不变
        self.assertEqual(self.tag1.description, "包括极限、导数等")

    def test_partial_update_tag(self):
        """测试部分更新标签"""
        data = {"description": "更新后的描述"}
        response = self.client.patch(self.detail_url(self.tag1.id), data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.tag1.refresh_from_db()
        self.assertEqual(self.tag1.description, "更新后的描述")

    def test_update_to_duplicate_combination(self):
        """测试更新时 name+category 与其他标签重复，应失败"""
        data = {
            "name": "函数",
            "category": "知识点"
        }
        # 尝试将 tag4 更新为与 tag1 重复
        response = self.client.put(self.detail_url(self.tag4.id), data, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('name', response.data)
        self.assertIn('该标签名称与分类的组合已存在。', str(response.data['name']))

    def test_delete_tag(self):
        """测试删除标签"""
        response = self.client.delete(self.detail_url(self.tag1.id))
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertFalse(Tag.objects.filter(id=self.tag1.id).exists())

    def test_filter_by_name(self):
        """测试按名称模糊查询"""
        response = self.client.get(self.list_url, {'name': '函'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        print("响应数据:", response.data)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['name'], '函数')

    def test_filter_by_category(self):
        """测试按分类模糊查询"""
        response = self.client.get(self.list_url, {'category': '知'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        print("响应数据:", response.data)
        results = [t['name'] for t in response.data['results']]
        self.assertIn('函数', results)
        self.assertIn('方程', results)
        self.assertIn('光合作用', results)
        self.assertEqual(len(response.data['results']), 3)

    def test_filter_by_exact_category(self):
        """测试按分类精确查询"""
        response = self.client.get(self.list_url, {'exact_category': '题型'})
        print("响应数据:", response.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['name'], '诗词鉴赏')

    def test_search_tags(self):
        """测试全文搜索"""
        response = self.client.get(self.list_url, {'search': '诗词'})
        print("响应数据:", response.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['name'], '诗词鉴赏')

    def test_ordering_by_used_count(self):
        """测试按使用次数升序排序"""
        response = self.client.get(self.list_url, {'ordering': 'used_count'})
        print("响应数据:", response.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        used_counts = [t['used_count'] for t in response.data['results']]
        self.assertEqual(used_counts, sorted(used_counts))

    def test_ordering_by_created_at_desc(self):
        """测试按创建时间降序排序（默认）"""
        response = self.client.get(self.list_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        print("响应数据:", response.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        created_ats = [t['created_at'] for t in response.data['results']]
        # 检查是否为降序
        self.assertEqual(created_ats, sorted(created_ats, reverse=True))

    def test_list_categories(self):
        """测试获取所有唯一分类"""
        response = self.client.get(self.categories_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        print("响应数据:", response.data)
        self.assertEqual(response.data['count'], 2)  # 知识点、题型
        expected = ['题型', '知识点']
        self.assertEqual(sorted(response.data['results']), sorted(expected))

    def test_list_by_category(self):
        """测试按分类获取标签列表"""
        response = self.client.get(self.by_category_url('知识点'))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        print("响应数据:", response.data)
        self.assertEqual(len(response.data['results']), 3)
        names = [t['name'] for t in response.data['results']]
        self.assertIn('函数', names)
        self.assertIn('方程', names)
        self.assertIn('光合作用', names)

    def test_list_by_category_not_found(self):
        """测试按分类查询但无结果"""
        response = self.client.get(self.by_category_url('化学'))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        self.assertEqual(response.data['detail'], '该分类下无标签。')

    def test_increment_used_count(self):
        """测试手动增加使用次数"""
        initial_count = self.tag1.used_count
        response = self.client.post(self.increment_url(self.tag1.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['used_count'], initial_count + 1)
        self.tag1.refresh_from_db()
        self.assertEqual(self.tag1.used_count, initial_count + 1)

    def test_increment_nonexistent_tag(self):
        """测试对不存在的标签调用 increment"""
        response = self.client.post(self.increment_url(999))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_update_readonly_fields(self):
        """测试尝试更新只读字段（如 used_count）应失败或被忽略"""
        data = {"used_count": 999}
        response = self.client.patch(self.detail_url(self.tag1.id), data, format='json')
        # 序列化器中 used_count 不在可写字段中，所以会被忽略
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.tag1.refresh_from_db()
        self.assertNotEqual(self.tag1.used_count, 999)  # 未被修改

    def test_create_with_null_category(self):
        """测试创建 category 为 null 的标签"""
        data = {
            "name": "易错题",
            "category": None,
            "description": "常见错误类型"
        }
        response = self.client.post(self.list_url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        tag = Tag.objects.get(id=response.data['id'])
        self.assertIsNone(tag.category)